﻿/******************************************************************************
 * 【本类功能概述】                                 					      *
 *  版权所有（C）2017-                                           *
 *  保留所有权利。                                                            *
 ******************************************************************************
 *  作者 : <Jiang Jiang>
 *  创建时间: 2018-3-28 11:21:35
 *  文件描述: 逻辑定时器类，支持timeScale
 *****************************************************************************/

using System;
using System.Collections.Generic;
using UnityEngine;
using Util;

public class Timer : MonoBehaviour
{
    private static Timer _ins;
    public static GameObject TimeObj;
    public ActionList updata = new ActionList();
    public ActionList Fupdata = new ActionList();
    public ActionList lateUpdate = new ActionList();
    public ActionList nextFrame = new ActionList();

    public static float timeScale
    {
        get
        {
            return Time.timeScale;
        }
        set
        {
            Time.timeScale = value;
        }
    }

    public static int FrameCount;

    public static bool IsNewFrame
    {
        get
        {
            if (Time.frameCount == FrameCount)
            {
                return true;
            }
            FrameCount = Time.frameCount;
            return false;
        }
    }

    public static Timer Instance
    {
        get
        {
            if (_ins == null)
            {
                TimeObj = new GameObject("Time");
                DontDestroyOnLoad(TimeObj);
                _ins = TimeObj.AddComponent<Timer>();
            }
            return _ins;
        }
    }

    [Serializable]
    public class TimerProc
    {
        public float duration;
        public float time;
        public int loop = -1;
        public bool beScale = true;

        [NonSerialized]
        public Action Call = delegate { };

        [NonSerialized]
        public bool isOwnTest = false;//对象检测

        [NonSerialized]
        public UnityEngine.Object own = null;//执行的对象

        public void SubTime(float deltaTime, float noScaleTime)
        {
            float t = beScale ? deltaTime : noScaleTime;
            this.time -= t;
        }
    }

    #region Clock

    [Serializable]
    public class TimerClock
    {
        public uint sole;
        public bool isok = false;
        public float maxcd = 0;
        public float cd = 0;
        public Action finish;

        public void Reset()
        {
            cd = 0;
            isok = false;
            if (!Instance.UpDataList.Contains(this))
            {
                Instance.UpDataList.Add(this);
            }
        }

        public void Destroy()
        {
            Instance.UpDataList.Remove(this);
        }
    }

    private Mlist<TimerClock> UpDataList;
    public Mdic<uint, TimerClock> TCAll;
    private uint sole = 10000;
    public uint Sole => sole++;

    public static TimerClock AddClocks(float MaxTime, uint sole = 0)
    {
        return Instance.AddClock(MaxTime, sole);
    }

    public TimerClock AddClock(float MaxTime, uint sole = 0)
    {
        TimerClock tc = null;
        if (sole == 0)
        {
            sole = Sole;
        }
        if (!TCAll.TryGetValue(sole, out tc))
        {
            tc = new TimerClock();
            tc.sole = sole;
            TCAll.Add(tc.sole, tc);
        }
        tc.maxcd = MaxTime;
        tc.cd = 0;
        tc.isok = false;
        if (!UpDataList.Contains(tc))
        {
            UpDataList.Add(tc);
        }

        return tc;
    }

    private void UpdataClock()
    {
        for (int i = UpDataList.Count - 1; i >= 0; i--)
        {
            var tc = UpDataList[i];
            tc.cd += Time.deltaTime;
            if (tc.cd >= tc.maxcd)
            {
                tc.isok = true;
                tc.finish.toInvoke();
                UpDataList.RemoveAt(i);
            }
        }
    }

    #endregion Clock

    private void Awake()
    {
        list = new List<TimerProc>();
        addList = new List<TimerProc>();
        removeList = new List<TimerProc>();
        Fixedlist = new List<TimerProc>();
        FixedaddList = new List<TimerProc>();
        FixedremoveList = new List<TimerProc>();

        UpDataList = new Mlist<TimerClock>();
        TCAll = new Mdic<uint, TimerClock>();
    }

    public void Dispose()
    {
        list.Clear();
        addList.Clear();
        removeList.Clear();
        Fixedlist.Clear();
        FixedaddList.Clear();
        FixedremoveList.Clear();
    }

    private List<TimerProc> list = null;
    private List<TimerProc> addList = null;
    private List<TimerProc> removeList = null;

    public static TimerProc Adds(float duration, int loop = 1, Action call = null, bool beScale = true)
    {
        return Instance.Add(duration, loop, call, beScale);
    }

    public static TimerProc Adds(float duration, Action call = null, bool beScale = true)
    {
        return Instance.Add(duration, 1, call, beScale);
    }
    public static TimerProc Adds(UnityEngine.Object Own, float duration, int loop, Action call, bool beScale = true)
    {
        return Instance.Add(Own, duration, 1, call, beScale);
    }
    public static TimerProc Adds(UnityEngine.Object Own, float duration, Action call, bool beScale = true)
    {
        return Instance.Add(Own, duration, 1, call, beScale);
    }

    public TimerProc Add(float duration, int loop, Action call, bool beScale)
    {
        TimerProc proc = new TimerProc();
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;
        proc.Call = call;
        proc.beScale = beScale;
        proc.isOwnTest = false;
        addList.Add(proc);
        return proc;
    }

    public TimerProc Add(UnityEngine.Object Own, float duration, int loop, Action call, bool beScale = true)
    {
        var tp = Add(duration, loop, call, beScale);
        tp.own = Own;
        tp.isOwnTest = true;
        return tp;
    }

    public TimerProc Add(UnityEngine.Object Own, float duration, Action call, bool beScale = true)
    {
        var tp = Add(duration, 1, call, beScale);
        tp.own = Own;
        tp.isOwnTest = true;
        return tp;
    }

    public TimerProc Add(float duration, int loop, Action call)
    {
        return Add(duration, loop, call, true);
    }

    public TimerProc Add(float duration, Action call)
    {
        return Add(duration, 1, call, true);
    }

    //下一帧
    public void NextFrame(Action call)
    {
        nextFrame.Add(call);
    }

    public TimerProc Add(float duration, List<Action> call)
    {
        TimerProc proc = null;
        int id = 0;
        proc = Add(duration, call.Count, () =>
        {
            call[id].Invoke();
            id++;
        });
        return proc;
    }

    public void Reset(object timer, float duration, int loop, Action call, bool beScale)
    {
        TimerProc proc = timer as TimerProc;
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;
        proc.Call = call;
        proc.beScale = beScale;

        if (!list.Contains(proc))
        {
            addList.Add(proc);
        }
    }

    public void Reset(object timer, float duration, int loop)
    {
        TimerProc proc = timer as TimerProc;
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;

        if (!list.Contains(proc))
        {
            addList.Add(proc);
        }
    }

    public void Remove(object proc)
    {
        if (proc == null)
        {
            return;
        }

        TimerProc tp = proc as TimerProc;
        removeList.Add(tp);
    }

    public void Update()
    {
        updata.Invoke();
        float deltaTime = Time.deltaTime;
        float fixTime = Time.unscaledDeltaTime;
        DelayAddTimer();
        DelayRemoveTimer();

        for (int i = list.Count - 1; i >= 0; i--)
        {
            TimerProc proc = list[i];
            proc.SubTime(deltaTime, fixTime);

            if (proc.time <= 0)
            {
                try
                {
                    if (proc.isOwnTest)//对象检测是否被销毁
                    {
                        if (proc.own.isTrue())
                        {
                            proc.Call();
                        }
                        else
                        {
                            list.RemoveAt(i);
                        }
                    }
                    else
                    {
                        proc.Call();
                    }
                }
                catch (Exception e)
                {
                    list.RemoveAt(i);
                    debug.log(e);
                    continue;
                }

                if (proc.loop > 0)
                {
                    --proc.loop;
                    proc.time += proc.duration;
                }

                if (proc.loop == 0)
                {
                    list.RemoveAt(i);
                }
                else if (proc.loop < 0)
                {
                    proc.time += proc.duration;
                }
            }
        }

        UpdataClock();
        nextFrame.Invoke();
        nextFrame.Clear();
    }

    private void LateUpdate()
    {
        debug.ShowCalculate();
        lateUpdate.Invoke();
    }

    private void DelayAddTimer()
    {
        if (addList.Count > 0)
        {
            list.AddRange(addList);
            addList.Clear();
        }
    }

    private void DelayRemoveTimer()
    {
        if (removeList.Count == 0)
        {
            return;
        }

        for (int i = 0; i < removeList.Count; i++)
        {
            list.Remove(removeList[i]);
        }

        removeList.Clear();
    }

    //------------------------------------------------------------------------------------------------------
    private List<TimerProc> Fixedlist = null;

    private List<TimerProc> FixedaddList = null;
    private List<TimerProc> FixedremoveList = null;

    public TimerProc AddFixed(float duration, int loop, Action call, bool beScale)
    {
        TimerProc proc = new TimerProc();
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;
        proc.Call = call;
        proc.beScale = beScale;
        FixedaddList.Add(proc);

        return proc;
    }

    public TimerProc AddFixed(float duration, int loop, Action call)
    {
        return AddFixed(duration, loop, call, false);
    }

    public TimerProc AddFixed(float duration, List<Action> call)
    {
        TimerProc proc = null;
        int id = 0;
        proc = AddFixed(duration, call.Count, () =>
        {
            call[id].Invoke();
            id++;
        });
        return proc;
    }

    public void ResetFixed(object timer, float duration, int loop, Action call, bool beScale)
    {
        TimerProc proc = timer as TimerProc;
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;
        proc.Call = call;
        proc.beScale = beScale;

        if (!Fixedlist.Contains(proc))
        {
            FixedaddList.Add(proc);
        }
    }

    public void ResetFixed(object timer, float duration, int loop)
    {
        TimerProc proc = timer as TimerProc;
        proc.duration = duration;
        proc.time = duration;
        proc.loop = loop;

        if (!Fixedlist.Contains(proc))
        {
            FixedaddList.Add(proc);
        }
    }

    public void RemoveFixed(object proc)
    {
        if (proc == null)
        {
            return;
        }

        TimerProc tp = proc as TimerProc;
        FixedremoveList.Add(tp);
    }

    public void FixedUpdate()
    {
        Fupdata.Invoke();
        float deltaTime = Time.fixedDeltaTime;
        float fixTime = Time.fixedUnscaledDeltaTime;
        DelayAddFixedTimer();
        DelayRemoveFixedTimer();

        for (int i = Fixedlist.Count - 1; i >= 0; i--)
        {
            TimerProc proc = Fixedlist[i];
            proc.SubTime(deltaTime, fixTime);

            if (proc.time <= 0)
            {
                try
                {
                    proc.Call();
                }
                catch (Exception e)
                {
                    Fixedlist.RemoveAt(i);
                    //Debugger.LogException("timer call exception:", e);
                    debug.log(true, debug.Level.error_3, "事件", proc.Call, "\n -------\n", e);
                    continue;
                }

                if (proc.loop > 0)
                {
                    --proc.loop;
                    proc.time += proc.duration;
                }

                if (proc.loop == 0)
                {
                    Fixedlist.RemoveAt(i);
                }
                else if (proc.loop < 0)
                {
                    proc.time += proc.duration;
                }
            }
        }
    }

    private void DelayAddFixedTimer()
    {
        if (FixedaddList.Count > 0)
        {
            Fixedlist.AddRange(FixedaddList);
            FixedaddList.Clear();
        }
    }

    private void DelayRemoveFixedTimer()
    {
        if (FixedremoveList.Count == 0)
        {
            return;
        }

        for (int i = 0; i < FixedremoveList.Count; i++)
        {
            Fixedlist.Remove(FixedremoveList[i]);
        }

        FixedremoveList.Clear();
    }
}